package indi.lqzhi.basic.tools.bean;

import androidx.annotation.NonNull;


import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import indi.lqzhi.basic.tools.lambda.function.Function;
import indi.lqzhi.basic.tools.lambda.function.Predicate;
import indi.lqzhi.basic.tools.lambda.function.Supplier;


/**
 * Copy Hutool SimpleCache
 */
public class Cache <K, V> implements Iterable<Map.Entry<K, V>>, Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 池
     */
    private final Map<K, V> pool;

    /**
     * 乐观读写锁
     */
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    /**
     * 写的时候每个key一把锁，降低锁的粒度
     */
    protected final Map<K, Lock> keyLockMap = new ConcurrentHashMap<>();

    /**
     * 构造，默认使用{@link WeakHashMap}实现缓存自动清理
     */
    public Cache() {
        this(new WeakHashMap<>());
    }

    /**
     * 构造 使用自定义的Map进行初始化
     * @param map map接口
     */
    public Cache(Map<K, V> map) {
        this.pool = map;
    }

    /**
     * 得到值
     * @param key 关键字
     * @return 值
     */
    public V get(K key) {
        lock.readLock().lock();
        try {
            return pool.get(key);
        } finally {
            lock.readLock().unlock();
        }
    }

    /**
     * 从缓存中获得对象，当对象不在缓存中或已经过期返回Consumer回调产生的对象
     *
     * @param key      键
     * @param supplier 如果不存在回调方法，用于生产值对象
     * @return 值对象
     */
    public V get(K key, Supplier<V> supplier) {
        return get(key, null, supplier);
    }

    /**
     * 从缓存中获得对象，当对象不在缓存中或已经过期返回Func0回调产生的对象
     *
     * @param key            键
     * @param validPredicate 检查结果对象是否可用，如是否断开连接等
     * @param supplier       如果不存在回调方法或结果不可用，用于生产值对象
     * @return 值对象
     */
    public V get(K key, Predicate<V> validPredicate, Supplier<V> supplier) {
        V v = get(key);
        if (null == v && null != supplier) {
            //每个key单独获取一把锁，降低锁的粒度提高并发能力，see pr#1385@Github
            final Lock keyLock = computeIfAbsent(keyLockMap,key,k ->  new ReentrantLock());
            keyLock.lock();
            try {
                // 双重检查，防止在竞争锁的过程中已经有其它线程写入
                v = pool.get(key);
                if (null == v || (null != validPredicate && !validPredicate.test(v))) {
                    try {
                        v = supplier.get();
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                    put(key, v);
                }
            } finally {
                keyLock.unlock();
                keyLockMap.remove(key);
            }
        }

        return v;
    }


    /**
     * 放入键值对
     *
     * @param key   关键字
     * @param value 值
     * @return 值
     */
    public V put(K key, V value) {
        // 独占写锁
        lock.writeLock().lock();
        try {
            pool.put(key, value);
        } finally {
            lock.writeLock().unlock();
        }
        return value;
    }


    /**
     * 移除值
     *
     * @param key 关键字
     * @return 移除的值
     */
    public V remove(K key) {
        // 独占写锁
        lock.writeLock().lock();
        try {
            return pool.remove(key);
        } finally {
            lock.writeLock().unlock();
        }
    }

    /**
     * 清空池
     */
    public void clear() {
        // 独占写锁
        lock.writeLock().lock();
        try {
            this.pool.clear();
        } finally {
            lock.writeLock().unlock();
        }
    }

    @NonNull
    @Override
    public Iterator<Map.Entry<K, V>> iterator() {
        return this.pool.entrySet().iterator();
    }

    public boolean containsKey(K key) {
        return this.pool.containsKey(key);
    }

    public boolean containsValue(V value) {
        return this.pool.containsValue(value);
    }

    /**
     * map的computeIfAbsent
     * @see Map
     */
    private <T> T computeIfAbsent(Map<K,T> map,K key,
                      Function<? super K, ? extends T> mappingFunction) {
        Objects.requireNonNull(mappingFunction);
        T v;
        if ((v = map.get(key)) == null) {
            T newValue;
            if ((newValue = mappingFunction.apply(key)) != null) {
                map.put(key, newValue);
                return newValue;
            }
        }
        return v;
    }

}
